iT邦幫忙

2025 iThome 鐵人賽

DAY 22
1
Software Development

30 天 Effective C++ 大挑戰!!系列 第 22

[Day 22] 中場休息 Q&A小測驗!!

  • 分享至 

  • xImage
  •  

又到了回顧學習成果的小測驗時間!跟著 Yoyo 一起沉浸在泛型程式設計的哲學中,探索 TMP 等高階模板技術吧。在正式開始前不妨先問問自己:是否全然理解模板在跨型別運算邏輯抽象性能優化的設計意圖?

Q1. 以下程式中的 doProcessing 函式有非模板版本 Widget 和模板版本 T,它們對介面要求的主要差異是什麼?

class Widget {
public:
    Widget();
    virtual ~Widget();
    virtual std::size_t size() const;
    virtual void normalize();
    void swap(Widget& other);
};

void doProcessing(Widget& w) {
    if (w.size() > 10 && w != someNastyWidget) {
        Widget temp(w);
        temp.normalize();
        temp.swap(w);
    }
}

template <typename T>
void doProcessing(T& w) {
    if (w.size() > 10 && w != someNastyWidget) {
        T temp(w);
        temp.normalize();
        temp.swap(w);
    }
}

A) 非模板版本需要「隱式介面」,而模板版本需要「顯式介面」。
B) 兩個版本都需要在類別宣告中定義明確的介面。
C) 非模板版本需要明確介面,而模板版本基於有效表達式需要「隱式介面」。
D) 模板版本需要「執行期多型」,而非模板版本需要「編譯期多型」。
E) 兩者要求相同的介面,並無任何差異。

非模板版本要求 w 提供由 Widget 類別明確指定的介面,即在類別中聲明的成員函式。而模板版本則不需要明確聲明介面,取而代之的是基於函式體中的表達式推斷出 T 必須支持的操作 —— 也就是「隱式介面」。

  • 隱式介面: 模板並不要求特定類型,但會在模板實例化檢查該類型是否支持函式內部使用的操作。這種方式讓模板具有更大的靈活性,但缺乏明確性容易導致潛在的編譯錯誤。 e.g. size()normalize()swap()
  • 顯式介面: 類別中具體聲明的成員函式和屬性,編譯器可以依據這些成員進行檢查。 以題目的例子來說,非模板版本強制要求傳入參數必須是 Widget 或其衍生類別,這個要求是明確的。

Q2. 為什麼在解析衍生類別模板時,C++ 編譯器無法找到基類模板中繼承的名稱?
A) 因為基類模板可能針對某些型別進行特殊化。
B) 因為基類模板都必須是抽象的。
C) 因為 C++ 不支援模板繼承。
D) 因為基類中的所有成員預設為 private
E) 因為衍生類別必須始終覆寫基類函式。

基底類別模板若對某些型別進行特殊化,可能不支援提供相同的介面。在解析衍生模板類別時,編譯器不會默認基底類別模板的所有特殊化能提供與主要模板相同的介面。除非有明確指示,否則在解析衍生模板類別時,C++ 編譯器不會自動查找基類模板中的名稱。

在模板繼承中,如果想在衍生類別中解析某函式或變數的名稱,解決方法是顯式地通過 this->BaseClassName::引用基類中的成員。C++ 的解析機制會在模板實例化之前和之後分兩階段進行,避免基類模板的特殊化導致潛在不一致問題。

template<typename T>
class Base {
protected:
    void foo() {}
};

template<typename T>
class Derived : public Base<T> {
    void bar() {
        // foo(); complie error...
        this->foo();
    }
};

Q3. 為什麼在 Rational<T> 類別模板中,會將非成員的 operator* 定義為友元函式?
A) 讓 operator* 訪問 Rational 的私有成員。
B) 可以避免必須在類別外部實現函式。
C) 為了確保函式始終是 inline。
D) 迫使 operator* 在所有參數上啟用隱式型別轉換。
E) 有效防止函式被重複實例化多次。

主要原因是允許在調用 operator* 時,包括 lvaule 和 rvalue 的所有參數都可以進行隱式類型轉換。將 operator* 宣告為友元且為非成員函式,能讓該操作符在參數上執行更廣泛的隱式轉換,這是非成員函式的特性。

成員函式的 lvaule 必須為實例,或能從其他類型顯式轉換,其隱式轉換僅適用於 rvalue。非成員函式的左rvalue 則可進行隱式轉換

class Rational {
    int numerator, denominator;
public:
    Rational(int num, int denom) : numerator(num), denominator(denom) {}
    friend Rational operator*(const Rational& lhs, const Rational& rhs);
};

Rational r1(1, 2);
Rational r2 = r1 * 2;

Q4. 模板元程式設計的主要優點是什麼?
A) TMP 允許 C++ 程式在不重新編譯的情況下運行於任何操作系統。
B) TMP 保障動態記憶體分配在執行期自動管理。
C) TMP 確保所有 STL 容器使用相同的記憶體分配器。
D) TMP 允許在執行期創建使用者自定義的型別。
E) TMP 可以將工作從執行期移到編譯期,從而提早檢測錯誤並提升執行期效能。

TMP 的主要優勢是將運算和檢查從執行期移到編譯期。這不僅可以提早發現型別錯誤,還能生成更高效且輕量的執行檔。

模板元程式設計的核心是利用 C++ 模板系統在編譯期進行計算和邏輯處理。常見應用包括型別屬性檢測、編譯期常量計算、與設計高度優化的容器行為。這使 TMP 成為高效編程的重要技術之一,儘管有時會導致程式碼的可讀性下降。


上一篇
[Day 21] Templates and Generic Programming IV
下一篇
[Day 23] Customizing new and delete I
系列文
30 天 Effective C++ 大挑戰!!30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言